package cn.suimg.neo4j.util;

import cn.hutool.core.bean.BeanUtil;
import cn.suimg.neo4j.annotation.MappingNeoGraph;
import cn.suimg.neo4j.annotation.OriginalId;
import cn.suimg.neo4j.bean.BaseTable;
import cn.suimg.neo4j.dto.NeoQueryResult;
import cn.suimg.neo4j.graph.NeoGraph;
import cn.suimg.neo4j.graph.UniversalGraph;
import cn.suimg.neo4j.relationship.NeoRelationship;
import cn.suimg.neo4j.relationship.UniversalRelationship;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.RelationshipEntity;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 关系图谱工具类
 */
@Slf4j
public class NeoGraphUtil {

    /**
     * 查询图谱的名称
     */
    private static final Function<NeoGraph,String> queryGraphLabels = neoGraph -> neoGraph.getClass().getAnnotation(NodeEntity.class).label();

    /**
     * 查询关系的类型
     */
    private static final Function<NeoRelationship,String> queryRelationshipType = neoRelationship -> neoRelationship.getClass().getAnnotation(RelationshipEntity.class).type();

    /**
     * 去重用的Map使用完之后需要clear掉
     */
    private static final Map<Object, Boolean> seenMap = new HashMap<>();

    /**
     * 去重的条件
     */
    private static final Predicate<Long> distinctByKey = key -> seenMap.putIfAbsent(key,Boolean.TRUE) == null;

    /**
     * MySQL对象转换Neo4j对象方法
     * @param bean
     * @param <T>
     * @return
     */
    public static <T extends NeoGraph> T convert2NeoGraph(BaseTable bean){
        try {
            MappingNeoGraph annotation = bean.getClass().getAnnotation(MappingNeoGraph.class);
            if(annotation == null)
                throw new RuntimeException("object can not mapping to graph class,because of no @MappingNeoGraph annotation or using inner class");

            Class<? extends NeoGraph> graphClass = annotation.value();
            NeoGraph graphInstance = graphClass.newInstance();

            for (Field field : graphClass.getDeclaredFields()) {
                field.setAccessible(true);
                if(null != field.getAnnotation(OriginalId.class))
                    field.set(graphInstance,bean.getId());
            }

            BeanUtil.copyProperties(bean,graphInstance,"id");
            return (T) graphInstance;
        }catch (InstantiationException | IllegalAccessException e){
            log.error("convert object to graph class error",e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 构建接口DTO
     * @param graphList
     * @param relationshipList
     * @return
     */
    public static NeoQueryResult buildWebResult(List<NeoGraph> graphList, List<NeoRelationship> relationshipList,Long total){

        //节点列表
        List<NeoQueryResult.NodeResult> nodeResultList = graphList.stream()
                .filter(graph -> distinctByKey.test(graph.getId()))
                .map(graph -> new NeoQueryResult.NodeResult() {{

                    if(graph instanceof UniversalGraph){
                        UniversalGraph universalGraph = (UniversalGraph) graph;
                        setId(universalGraph.getId());
                        setLabel(universalGraph.getLabel());
                        setGraph(universalGraph.getGraph());
                        setProperties(universalGraph.getProperties());
                    }else{
                        setId(graph.getId());
                        setLabel(queryGraphLabels.apply(graph));
                        setGraph("");
                        Map<String, Object> properties = BeanUtil.beanToMap(graph);
                        properties.remove("id");
                        setProperties(properties);
                    }
                }}).collect(Collectors.toList());
        seenMap.clear();

        //关系列表
        List<NeoQueryResult.RelationshipResult> relationshipResultList = relationshipList.stream()
                .filter(relationship -> distinctByKey.test(relationship.getId()))
                .map(relationship -> new NeoQueryResult.RelationshipResult() {{

                    if(relationship instanceof UniversalRelationship){
                        UniversalRelationship universalRelationship = (UniversalRelationship) relationship;
                        setId(universalRelationship.getId());
                        setStartNode(universalRelationship.getStartNode().getId());
                        setEndNode(universalRelationship.getEndNode().getId());
                        setType(universalRelationship.getLabel());
                        setProperties(universalRelationship.getProperties());
                    }else {
                        setId(relationship.getId());
                        setStartNode(relationship.getStartNode().getId());
                        setEndNode(relationship.getEndNode().getId());
                        setType(queryRelationshipType.apply(relationship));

                        Map<String, Object> properties = BeanUtil.beanToMap(relationship);
                        properties.remove("startNode");
                        properties.remove("endNode");
                        properties.remove("id");
                        setProperties(properties);
                    }
                }}).collect(Collectors.toList());
        seenMap.clear();

        return new NeoQueryResult(){{
            setNodes(nodeResultList);
            setRelationships(relationshipResultList);
            setTotal(total);
        }};
    }
}
